// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3.js
// @output_file_name default.js
// ==/ClosureCompiler==

/**
 * Queensland/Australia 2011 Flood map.
 * @author James McGill (jmcgill@google.com)
 */

/**
 * How often data sources should be refreshed, in milliseconds.
 */
var DATA_REFRESH_MS = 5 * 60 * 1000;

/**
 * URL of a transparent image to use for tiles outside the bounds of one of the
 * image overlays.
 * @type {string}
 */
var EMPTY_TILE_URL = 'http://maps.gstatic.com/mapfiles/qldfloods/n.png';

/**
 * Default contents of the Search input field in lieu of a static label.
 * @type {string}
 */
var SEARCH_PROMPT = "Search";

/**
 * Loaded state for a XMLHttpRequest, as per .readyState.
 *
 * Defined in http://www.w3.org/TR/2006/WD-XMLHttpRequest-20060405/.
 *
 * @type {int}
 */
var XHR_STATE_LOADED = 4;


/**
 * Track whether initialisation of the application is complete.
 * @type {boolean}
 */
var appInitialised = false;

/**
 * The list of all toggle buttons controlling drop downs.
 * @type {Array.<Node>}
 */
var dropDownToggleButtons_ = [];

/**
 * Single geocoder instance. Cached and re-used multiple times.
 * @type {Object}
 */
var geocoder;

/**
 * Information 'bubble' on the map.
 * @type {google.maps.InfoWindow}
 */
var iw = new google.maps.InfoWindow();

/**
 * Groups of KML layers to show/hide together, to simplify the UI.
 * @type {Array.<Object>}
 */
var kmlGroups = [{
    name: 'Evacuation Centres',
    layers: ['qldevac', 'vicevac']
  }, {
    name: 'Road Closures',
    layers: ['qldroadclosures', 'vicroadclosures', 'nswroadclosures']
  }, {
    name: 'SES Alerts',
    layers: ['vicses', 'nswses']
  }, {
    name: 'Power Outages',
    layers: ['qldpoweroutages', 'vicelecpowercor']
  }, {
    name: 'Photos from the Brisbane Floods (Yellow)',
    layers: ['brisphotos']
  }, {
    name: 'Cloud Imagery',
    layers: ['clouds']
  }
];

/**
 * KML Layers to automatically add. Each layer should be a 4-tupple of:
 * [Name, URL, Attribution Text, Attribution Link]
 *
 * Please do not change the order of these KML layers - doing so WILL BREAK
 * people's existing bookmarks. Use the KML Groups variable to change the
 * order in the UI.
 *
 * @type {Array.Array.<string>}
 *
 * TODO(bst): Ideally I'd like to transition to using names for the layers
 * in bookmarks..
 */
var kml = [
  ["Queensland Evacuation Centres",
   "http://mapvisage.appspot.com/static/floodmap/evaccentre.kml",
   "Evacuation Centres (ABC, BCC, Qld Police)",
   "",
   "kml",
   "qldevac"
  ],
  ["Queensland Road Closures (Pink)",
   "http://mapvisage.appspot.com/kml/qldroadclosures",
   "Queensland Road Closures, Queensland Government",
   "http://highload.131940.qld.gov.au/",
   "kml",
   "qldroadclosures"
  ],
  ["Victorian Evacuation Centres",
   "http://mapvisage.appspot.com/kml/vicevac",
   "Victorian Evacuation Centres (DHS, ABC)",
   "http://www.abc.net.au/local/stories/2011/01/14/3113287.htm?site=melbourne",
   "kml",
   "vicevac"
  ],
  ["Victorian Road Closures",
   "http://mapvisage.appspot.com/kml/vicroadclosures",
   "Victorian Road Closures, VicRoads",
   "http://www.vicroads.vic.gov.au",
   "kml",
   "vicroadclosures"
  ],
  ["Queensland Power Outages",
   "http://bst.latest.mapvisage.appspot.com/kml/qldpoweroutages",
   "Queensland Power Outages (Energex)",
   "http://www.energex.com.au/network/asp/unplanned_interruptions.asp",
   "kml",
   "qldpoweroutages"
  ],
  ["Affected towns and cities (ABC)",
   "http://maps.google.com/maps/ms?ie=UTF8&msa=0&output=kml&msid=210606325855659952999.000498e8d8ebf782c3d80",
   "Affected towns and cities, ABC",
   "http://www.abc.net.au",
   "kml",
   "abcaffected"
  ],
  ["Worst affected areas (SBS)",
   "http://maps.google.com/maps/ms?ie=UTF8&msa=0&output=kml&msid=216305641036137584677.000498fa830661a4cbafb",
   "Worst affected areas, SBS",
   "http://www.sbs.com.au",
   "kml",
   "sbsaffected"
  ],
  ["Photos from the Brisbane Floods (Yellow)",
   "http://mapvisage.appspot.com/static/floodmap/brisphotos2.kmz",
   "Photos from the Queensland Floods (Above Photography)",
   "http://www.abovephotography.com.au",
   "kml",
   "brisphotos"
  ],
  ["Western Victorian Power Outages",
   "http://mapvisage.appspot.com/kml/vicelec",
   "Victorian Power Outages (Powercor)",
   "http://www.powercor.com.au/Power_Outages/Powercor_Outages/Powercor_-_Network_Wide/",
   "kml",
   "vicelecpowercor"
  ],
  ["Victorian SES Alerts",
   "http://mapvisage.appspot.com/kml/vicses",
   "Victorian SES Flood Alerts",
   "http://www.ses.vic.gov.au",
   "kml",
   'vicses'
  ],
  ["NSW Alerts",
   "http://mapvisage.appspot.com/kml/nswses",
   "NSW Flood Alerts",
   "http://www.bom.gov.au/nsw",
   "kml",
   'nswses'
  ],
  ["New South Wales Road Closures",
   "http://mapvisage.appspot.com/kml/nswroadclosures",
   "New South Wales Road Closures, NSW RTA",
   "http://livetraffic.rta.nsw.gov.au",
   "kml",
   "nswroadclosures"
  ],
  ["Clouds",
   "http://mapvisage.appspot.com/kml/clouds",
   "United States Naval Research Laboratory",
   "http://www.nrl.navy.mil",
   "kml",
   "clouds"
  ]
];

/**
 * Layers, as constructed by the code at runtime.
 * @type {Array.<google.maps.ImageMapType, google.maps.KmlLayer>}
 */
var layers = {};

/**
 * Dictionary of KML layer name to index in layers[].
 * @type {Object}
 */
var layerMap = {};

/**
 * Google Maps API object
 * @type {google.maps.Map}
 */
var map;

/* TODO(bst): Document this! */
var events = {};

function getUrlParams() {
  var params = [];
  // Get current center and zoom so that we do not reset the map on
  // refresh.
  var center = map.getCenter();
  if (center) params.push('center=' + center.lat() + ',' + center.lng());
  var zoom = map.getZoom();
  if (zoom != null) params.push('zoom=' + map.getZoom());
  var layersValues = [];
  for (var layerName in layers) {
    if (layers[layerName] && layers[layerName].getMap && layers[layerName].getMap()) {
      layersValues.push(layerName);
    }
  }
  params.push('layers=' +  layersValues.join(','));
  return params.join('&');
}

function parseUrlParams() {
  var url = window.location.href;
  // Uses the hash parameters if present.  Otherwise looks for parameters
  // following the ?.  If both # and ? are present, use the parameters
  // following #.
  var hash = url.indexOf('#');
  if (hash != -1) {
    url = url.substring(hash + 1);
  } else {
    url = url.substring(url.indexOf('?') + 1);
  }
  var paramPairs = url.split('&');
  var params = {};
  for (var i = 0, I = paramPairs.length; i < I; ++i) {
    var paramSplit = paramPairs[i].split('=');
    var paramName = paramSplit[0];
    var paramValue = true;
    if (paramSplit.length == 2) {
      paramValue = paramSplit[1];
    }
    params[paramName] = paramValue;
  }

  return params;
}

/**
 * Parse a 'lat,lng' string into a LatLng object.
 *
 * TODO(bst): Check error conditions with parseFloat().
 *
 * @param {string} center  String to parse. Should be in 'lat,lng' format.
 * @return {google.maps.LatLng}  Coordinates specified, or null if invalid.
 */
function parseCenter(center) {
  if (center) {
    var latLngString = center.split(',');
    if (latLngString.length == 2) {
      var lat = parseFloat(latLngString[0]);
      var lng = parseFloat(latLngString[1]);
      return new google.maps.LatLng(lat, lng);
    }
  }
  return null;
}

/**
 * Retrieve the size of the map viewport.
 *
 * TODO(bst): Use the viewport size API
 *
 * @return {google.maps.Size}  Size of the map viewport in pixels.
 */
function getMapSize() {
  var map_inner = document.getElementById("map_canvas");
  return new google.maps.Size(map_inner.offsetWidth, map_inner.offsetHeight);
}

/**
 * Initialise the application on page load.
 *
 * This will set a delayed timer for the actual initialisation.
 * TODO(bst): Why delayed?
 */
function initialize() {
  window.setTimeout(initialize2, 25);
}

/**
 * Update the anchor component of the current page URL to represent the state of
 * the map (latitude, longitude, zoom level and current layers).
 */
function updatePageAnchor() {
  window.location.hash = getUrlParams();
}

/**
 * Initialize the map.
 */
function initialize2() {
  var myOptions = {
    disableDefaultUI: true,
    navigationControl: true,
    scaleControl: getMapSize().width > 400 ? true : false,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  }
  map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

  // Updates the window location hash when the map is done with a zoom or
  // center change.  Listens to idle to prevent changing the value during a
  // drag.
  google.maps.event.addListener(map, 'idle', updatePageAnchor);

  var params = parseUrlParams();
  var myLatlng = parseCenter(params['center']);
  if (myLatlng) {
    var zoom = parseInt(params['zoom']);
    map.setCenter(myLatlng);
    map.setZoom(typeof zoom == 'number' ? zoom : 6);
  } else {
    // Fit to bounds of QLD if the center wasn't specified in the URL
    // parameters.
    var bounds = new google.maps.LatLngBounds(
        new google.maps.LatLng(-29, 137),
        new google.maps.LatLng(-16, 154));
    map.fitBounds(bounds);
  }

  var layersParam = params['layers'];
  var layersToShow;
  if (layersParam != null && layersParam != "") {
    layersToShow = layersParam.split(',');
  } else {
    layersToShow = ['Rivers', layerMap['qldroadclosures'], layerMap['vicroadclosures'], layerMap['nswroadclosures'], layerMap['brisphotos']];
  }

  // Add our custom UI.
  addLegend();
  addAttribution();
  addViewButton(layersToShow);
  addSearchBar();

  // Add tiled overlay.
  //initTileOverlay('Future', 'http://maps.gstatic.com/mapfiles/qldfloods/bcc-projected-20110113/', '/', false, -27.7624544568, 152.449862619, -27.3016916566, 153.213490071, 6, 15, [-27.471311398711347, 153.02791748804933, 11, 'Brisbane'], true);
  initTileOverlay('B', 'http://mw1.google.com/crisisresponse/2011/australia/flood/geoeye/BundabergAustralia_40cm-2011-01-13_maptiles/', '_', true, -25.062906723828544, 152.24217224121097, -24.767104762792147, 152.45640563964847, 12, 20, [-24.867454829905377,152.34453312695314, 13, 'Bundaberg'], false);
  //initTileOverlay('Echuca', 'http://maps.gstatic.com/mapfiles/qldfloods/echuca-20110116/', '/', false, -36.1584407575, 144.712945092, -36.1049591171, 144.756658205, 6, 17, [-36.1316999373, 144.73480164850002, 12, 'Echuca'], true);
  initTileOverlay('QldCyclone', 'http://maps.gstatic.com/mapfiles/qldfloods/qldcyclone-20110201/', '/', false, -26.3935205, 139.5029470, -7.4803150, 166.6681269, 3, 10, [-24.2260, 144.7529, 5, 'Queensland'], true);
  initLayers();

  for (var i = 0; i < layersToShow.length; ++i) {
    var name = layersToShow[i];
    var layer = layers[name];
    if (layer) addLayer(name, layer);
  }

  // All loaded.
  appInitialised = true;
}

function initTileOverlay(layerName, prefix, sep, geoeye, lat1, lng1, lat2, lng2, zoom1, zoom2, zoomTo, invY) {
  var overlayBounds = new google.maps.LatLngBounds(new google.maps.LatLng(lat1, lng1), new google.maps.LatLng(lat2, lng2));

  var overlayZoomMin = zoom1;
  var overlayZoomMax = zoom2;

  var tileMap = new google.maps.ImageMapType({
      opacity: geoeye ? 0.85 : 0.60,
      isPng: !geoeye,
      tileSize: new google.maps.Size(256, 256),
      getTileUrl: function(tile, zoomLevel) {
        var tileProj = map.getProjection();

        if ((zoomLevel > overlayZoomMax) || (zoomLevel < overlayZoomMin)) {
          return EMPTY_TILE_URL;
        } else {
          var y = invY ? ((1 << zoomLevel) - tile.y - 1) : tile.y;

          var point1 = new google.maps.Point(256 * (tile.x / (1 << (zoomLevel))), 256 * ((tile.y + 1) / (1 << (zoomLevel))));
          var point2 = new google.maps.Point(256 * ((tile.x + 1) / ( 1 << (zoomLevel))), 256 * (tile.y / (1 << (zoomLevel))));

          var tileBounds = new google.maps.LatLngBounds(
              tileProj.fromPointToLatLng(point1, zoomLevel),
              tileProj.fromPointToLatLng(point2, zoomLevel));

          if (overlayBounds.intersects(tileBounds)) {
            if (geoeye)
              return prefix + tile.x + sep + y + sep + zoomLevel + '.jpg';
            else
              return prefix + zoomLevel + sep + tile.x + sep + y + '.png';
          } else {
            return EMPTY_TILE_URL;
          }
        }
      }});

  tileMap.setMap = function(my_map) {
    this.map_ = my_map;
    // Update the URL hash to reflect whether this layer is showing or
    // hidden.
    updatePageAnchor();
    var overlayFound = false;

    for (var i = 0; i < map.overlayMapTypes.getLength(); ++i) {
      if (this == map.overlayMapTypes.getAt(i)) {
        overlayFound = i;
        break;
      }
    }

    if ((overlayFound !== false) && !my_map) {
      map.overlayMapTypes.removeAt(i);
    } else if ((overlayFound === false) && my_map) {
      map.overlayMapTypes.push(this);
    }

    if (zoomTo && my_map && appInitialised) {
      var shouldAsk = (map.getZoom() < (zoomTo[2] - 1)) || !overlayBounds.intersects(map.getBounds());
      var shouldZoom = false;
      if (shouldAsk)
        shouldZoom = confirm("Zoom in to " + zoomTo[3] + "?");

      if (shouldZoom) {
        map.setCenter(new google.maps.LatLng(zoomTo[0], zoomTo[1]));  // -24.867454829905377,152.34453312695314
        map.setZoom(zoomTo[2]);
      }
    }
  }
  tileMap.getMap = function() {
    return this.map_;
  };

  layers[layerName] = tileMap;
}

/**
 * Initializes all of the KML and Fusion Tables layers, adding a hash to the
 * end of the KML URLs to ensure it is refreshed every 5 minutes.
 */
function initLayers() {
  document.getElementById("other_layers").innerHTML = '';

  /**
   * Adds the new layer to the layers map.  If a layer by that name already
   * existed, set the new layer's map to the old layer's and then remove the
   * old layer from the map.
   */
  function addLayerByName(name, newLayer) {
    var oldLayer = layers[name];
    if (oldLayer) {
      newLayer.setMap(oldLayer.getMap());
      oldLayer.setMap(null);
    }
    layers[name] = newLayer;

    google.maps.event.addListener(newLayer, 'map_changed', updatePageAnchor);
  }

   // Current 5 minute block to avoid caching.
  var now = new Date();

  // Milliseconds since this line was written.
  var since = now.getTime() - 1294794959132;

  // 300000 milliseconds in 5 minutes. This gives a number which increases
  // once every 5 minutes.
  var block = Math.floor(since / DATA_REFRESH_MS);

  // NOTE(jmcgill): Now using KML instead of markers.
  var rivers = new google.maps.KmlLayer("http://mapvisage.appspot.com/flood/json9?" + block, {
      suppressInfoWindows: true,
      preserveViewport: true
  });
  addLayerByName('Rivers', rivers);
  google.maps.event.addListener(rivers, "click", showInfowindow);

  // Add all other kmls.
  for (var i = 0; i < kml.length; ++i) {
    var spec = kml[i];
    var escapeChar = spec[1].indexOf('?') == -1 ? '?' : '&';

    var doc = null;
    if (spec[4] == "kml") {
      var options = {
        preserveViewport: true
      };
      if (spec[5] == 'clouds') {
        options['suppressInfoWindows'] = true;
      }
      doc = new google.maps.KmlLayer(spec[1] + escapeChar + block, options);
    } else if (spec[4] == "ft") {
      doc = new google.maps.FusionTablesLayer(spec[1]);
    }
    layerMap[spec[5]] = i;
    addLayerByName(i, doc);

    // Append to attributions.
    if (spec[2]) {
      document.getElementById("other_layers").innerHTML +=
        '<br><a href="' + spec[3] + '">' + spec[2] + '</a>';
    }
  }

  refreshLayers();
}


/**
 * Refresh the map layers with new data from the KML files.
 */
function refreshLayers() {
  window.setTimeout(initLayers, DATA_REFRESH_MS);
}

/**
  * Request a URL using XMLHttpRequest or platform-specific objects if required.
  *
  * IE5 and IE6 require dirty hacks (COM objects) to request web content, as they
  * don't support XMLHttpRequest. Pick the right object type, construct it, and
  * fetch the information for a callback routine.
  *
  * @param {string} url  URL to fetch (via a GET method)
  * @param {function} callback  Routine to invoke with the data, if the request
  *     to the webserver was successful.
  */
function requestUrl(url, callback) {
  var request;

  /* Construct the right XMLHttpRequest object.. */
  try {
    request = new ActiveXObject("MSXML2.XMLHTTP");
  } catch (NotIE5) {
    try {
      request = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (NotIE6) {
      request = new XMLHttpRequest();
    }
  }

  /* Closure to only invoke the callback if the request is successful. */
  request.onreadystatechange = function() {
    if (XHR_STATE_LOADED == request.readyState) {
      // We expect a JSON object in return. Safe(ish) to eval it.
      // bst: Not that safe, really. Every time you eval() JSON, you kill a
      // kitten.
      eval('response = ' + request.responseText);
      callback(response);
    }
  }

  request.open("GET", url);
  request.send("");
}


/**
 * Show an infowindow at the clicked point. Supresses the previously open
 * infowindow, allowing us to show only one infowindow even though we have
 * multiple KML files.
 * @param {google.maps.KmlMouseEvent} e  Event passed in by Maps API
 */
function showInfowindow(e) {
  var parts = e.featureData.description.split(';');
  var iw_text = '<div style="font-family: Arial, sans-serif"><div style="font-size: 14px; font-weight: bold;">' +
      e.featureData.name + '</div><div style="font-size: 13px;">' +
      '<b>River Height:</b> ' + parts[0] + '<br>' +
      '<b>Tendency:</b> ' + parts[1] + '<br>' +
      '<b>Time:</b> ' + parts[2];

      // Only present in v > 9
      if (parts.length > 3) {
        iw_text += '<br><b>Flood class: </b>' + parts[3];
      }
      iw_text += '</div>';
  var opts = {
    position: e.latLng,
    pixelOffset: e.pixelOffset,
    content: iw_text
  };
  iw.setOptions(opts);
  iw.open(map);
}


/**
 * Add a geocoding search bar to the map.
 */
function addSearchBar() {
  geocoder = new google.maps.Geocoder();

  // We need an outer div so we don't hit copyrights.
  var parentDiv = document.createElement('div');
  parentDiv.style.marginBottom = '25px';

  var div = document.createElement('div');
  div.className = 'searchouter';

  var button = document.createElement('input');
  button.type = 'button';
  button.value = 'Search';
  button.className = 'searchbutton';

  var input = document.createElement('input');
  input.className = 'searchbox';
  input.value = SEARCH_PROMPT;

  google.maps.event.addDomListener(input, 'keypress', function(e) {
    if (e.keyCode == 13) search(input.value);
  });

  // When we select the textbox, remove sample text.
  google.maps.event.addDomListener(input, 'focus', function() {
    if (input.value == SEARCH_PROMPT) input.value = "";
    input.style.color = "#000000";
  });

  google.maps.event.addDomListener(input, 'blur', function() {
    if (input.value == "") {
      input.value = SEARCH_PROMPT;
      input.style.color = "#777777";
    }
  });

  // On click, search.
  google.maps.event.addDomListener(button, 'click', function() {
      search(input.value);
  });

  // Add to map.
  div.appendChild(input);
  div.appendChild(button);
  if (getMapSize().width > 400) {
    parentDiv.appendChild(div);
    map.controls[google.maps.ControlPosition.BOTTOM].push(parentDiv);
  } else {
    parentDiv.className = 'controlparent';

    var button = createButton('Search', 70, true);
    parentDiv.appendChild(button);

    var dropDown = document.createElement('div');
    dropDown.style.position = 'absolute';
    dropDown.style.display = 'none';
    dropDown.style.left = '0px';
    dropDown.style.width = '275px';
    dropDown.appendChild(div);
    parentDiv.appendChild(dropDown);

    makeToggleButton(button, dropDown);

    map.controls[google.maps.ControlPosition.TOP_RIGHT].push(parentDiv);
  }
}

/**
 * Geocode the contents of the search bar, and center the map at that
 * location. Search terms are suffixed with Australia as a hint to the
 * Geocoder.
 * @param {string} text Text to search for.
 */
function search(text) {
  if (text == SEARCH_PROMPT) return;
    if (geocoder) {
      geocoder.geocode({address: text + ", Australia"}, function(results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
          map.fitBounds(results[0].geometry.viewport);
        } else {
          alert("The location you entered could not be found.");
        }
      });
    }
}

/**
 * Create a Google Maps style button.
 * @param {string} text The text to put on the button.
 * @param {number} width The width of the button in pixels.
 */
function createButton(text, width, opt_dropdown) {
  var outerDiv = document.createElement('div');
  var innerDiv = document.createElement('div');
  outerDiv.className = 'control';
  innerDiv.className = 'control-inner';
  outerDiv.style['width'] = width + 'px';
  innerDiv.innerHTML = text;
  if (opt_dropdown) innerDiv.innerHTML += ' <img src="down-arrow.png">';
  outerDiv.appendChild(innerDiv);
  return outerDiv;
}

/**
 * Toggle a button. This updates the style of the button to make it look
 * pressed/unpressed.
 * @param {Node} button The button div.
 * @return {boolean} True if the button is pressed after being toggled.
 */
function toggleButton(button) {
  var inner = button.childNodes[0];
  var selected = !button.selected_;  // Toggle selectedness.
  button.selected_ = selected;
  inner.className = selected ? 'selected-inner' : 'control-inner';
  return selected;
}

/**
 * Makes a toggle button which hides and shows a drop down.
 * @param {Node} button  The button element.
 * @param {Node} dropDown  The drop down to show or hide.
 */
function makeToggleButton(button, dropDown) {
  // Listen to mouse events on the button.
  google.maps.event.addDomListener(button, 'click', function() {
    resetDropDownToggleButtons(button);
    var selected = toggleButton(button);
    if (selected) {
      dropDown.style.display = "block";
    } else {
      dropDown.style.display = "none";
    }
  });
  button.dropDown_ = dropDown;
  dropDownToggleButtons_.push(button);
}

/**
 * Resets all toggle buttons on the page.
 * param {Node} opt_exclude  A button to exclude during reset.
 */
function resetDropDownToggleButtons(opt_exclude) {
  for (var i = 0, button; button = dropDownToggleButtons_[i]; ++i) {
    if (button.selected_ && button != opt_exclude) {
      toggleButton(button);
      button.dropDown_.style.display = 'none';
    }
  }
}

/**
 * Add the button to display attribution.
 */
function addAttribution() {
  var attributions = document.getElementById('attributions');

  var size = getMapSize();
  var parentDiv = document.createElement('div');
  var button, dash, position;
  var text = 'Information Sources';
  if (size.width > 500) {
    button = createButton(text, 125);
    position = google.maps.ControlPosition.TOP_RIGHT;
    parentDiv.className = 'controlparent';
  } else {
    button = document.createElement('span');
    button.style.textDecoration = 'underline';
    button.style.color = '#7777cc';
    if (button.innerText != 'undefined') button.innerText = text;
    if (button.textContent != 'undefined') button.textContent = text;

    position = google.maps.ControlPosition.BOTTOM_LEFT;
    parentDiv.className = 'link';
  }

  google.maps.event.addDomListener(button, 'click', function() {
    attributions.style.display = 'block';
  });

  var close = document.getElementById('close');
  google.maps.event.addDomListener(close, 'click', function() {
    attributions.style.display = 'none';
  });

  parentDiv.appendChild(button);
  if (dash) parentDiv.appendChild(dash);
  map.controls[position].push(parentDiv);
}

function arrayContains(a, obj) {
  for (var i = 0, I = a.length; i < I; ++i) {
    if (obj == a[i]) {
      return true;
    }
  }
  return false;
}

/**
 * Add the button to select view.
 */
function addViewButton(layersToShow) {
  var parentDiv = document.createElement('div');
  var button = createButton('More Layers', 105, true);

  var dropDown = document.createElement('div');
  dropDown.className = 'dropdown';
  dropDown.style.display = "none";
  dropDown.style.left = '0px';

  var views = [
    ['Rivers', 'River levels'],
//    ['Echuca', 'Predicted flood levels (Echuca)'],
//    ['Future', 'Predicted flood levels (Brisbane)'],
    ['B', 'Images of Bundaberg Flooding'],
    ['QldCyclone', 'Queensland Cyclone Forecasts']
  ];

  for (var i = 0; i < kmlGroups.length; ++i) {
    views.push([kmlGroups[i].layers, kmlGroups[i].name]);
  }

  for (var i = 0; i < views.length; ++i) {
    var shortName = views[i][0];
    var optSet = true;

    if (shortName.length && (typeof shortName === 'object')) {
      for (var j = 0; j < shortName.length; ++j) {
        optSet &= arrayContains(layersToShow, shortName[j]) || arrayContains(layersToShow, layerMap[shortName[j]]);
      }
    } else
      optSet = arrayContains(layersToShow, shortName);

    option = createMarginOption(views[i][1], optSet);

    // Hide KML layer with this name when unchecked.
    google.maps.event.addDomListener(option, 'click', function(option, id) {
      return function() {
        var checked = option.childNodes[0].checked;

        if (typeof id !== 'object')
          id = [id];

        for (var k = 0; k < id.length; ++k) {
          if (!(id[k] in layers))
            var layer = layers[layerMap[id[k]]];
          else
            var layer = layers[id[k]];
          if (checked) {
            addLayer(id[k], layer);
          } else {
            layer.setMap(null);
          }
        }
      }
    }(option, shortName));
    dropDown.appendChild(option);
  }

  makeToggleButton(button, dropDown);

  // Append the control to the map.
  parentDiv.appendChild(button);
  parentDiv.appendChild(dropDown);
  parentDiv.className = 'controlparent';
  map.controls[google.maps.ControlPosition.TOP_RIGHT].push(parentDiv);

  // Drop down by default.
  // toggleButton(button);
  // dropDown.style.display = "block";
  }


/**
 * Add a new layer to the map.
 *
 * @param {int}  Event handler index.
 * @param {google.maps.KmlLayer}  Layer to show.
 */
function addLayer(index, layer) {
  // Make roads appear on top of all KML layers.
  // NOTE(jmcgill): This hammers Maps, so removed for now.
  /*
  if (layer && layer.getMetadata) {
    events[index] = google.maps.event.addListener(
        layer, 'defaultviewport_changed', function(idx) {
          return function (e2) {
            layersToReadd = ['qldroadclosures', 'brisphotos'];

            // Re-add layers if already added.
            for (i = 0; i < layersToReadd; ++i) {
              layerId = layerMap[layersToReadd[i]];
              if (layers[layerId].setMap && layers[layerId].getMap && layers[layerId].getMap()) {
                // Remove and re-add layer asynchronously.
                layers[layerId].setMap(null);
                window.setTimeout(function() {
                layers[layerId].setMap(map); }, 0);
              }
            }
            google.maps.event.removeListener(events[idx]);
          }
        }(index));
  }
  */
  layer.setMap(map);
}


/**
 * Create the drop down options for displaying margins.
 * @return {Node} The node which was created.
 */
function createMarginOption(text, checked) {
  var outerLabel = document.createElement('div');
  var input = document.createElement('input');
  var label = document.createElement('span');
  input.className = 'margininput';
  input.type = 'checkbox';
  input.checked = checked;
  label.className = 'marginlabel';
  label.innerHTML = text;
  outerLabel.className = 'marginouter';
  outerLabel.appendChild(input);
  outerLabel.appendChild(label);
  return outerLabel;
}


/**
 * Add a legend to the map
 */
function addLegend() {
  var parentDiv = document.createElement('div');
  parentDiv.className = 'controlparent';
  parentDiv.setAttribute('controlheight', 0);

  var legend = document.createElement('div');
  legend.className = 'legend';

  var amounts = {
    "Major Flooding": "#FF0000",
    "Moderate Flooding": "#FFA938",
    "Minor Flooding": "#1C8A00",
    "Below Flood Level": "#40DDF5",
    "No Classification": "#CCCCCC"
  };

  /*
  var amounts = {
    "100+mm":"#C0262E",
    "50 to 99mm":"#0E4EBC",
    "25 to 49mm":"#FFCC33",
    "10 to 24mm":"#008856",
    ">0.2 to 9mm":"#888888",
    "0mm":"#000000"};
  */

  var textDiv = document.createElement('div');
  textDiv.innerHTML = "River Levels Legend";
  textDiv.style.fontSize = '12px';
  textDiv.style.fontWeight = 'bold';
  legend.appendChild(textDiv);

  for (var key in amounts) {
    label = createLegendLabel(key, amounts[key]);
    legend.appendChild(label);
  }

  var disclaimer = document.createElement('div');
  disclaimer.innerHTML = "Levels and locations are approximate";
  disclaimer.style.fontSize = '10px';
  disclaimer.style.color = '#555555';
  legend.appendChild(disclaimer);

  if (getMapSize().height > 400 && getMapSize().width > 350) {
    // Append the control to the map.
    // parentDiv.appendChild(button);
    parentDiv.appendChild(legend);
  } else {
    var button = createButton('Legend', 80, true);
    parentDiv.appendChild(button);

    var dropDown = document.createElement('div');
    dropDown.className = 'dropdown';
    dropDown.style.display = 'none';
    dropDown.style.right = '0px';
    dropDown.appendChild(legend);
    parentDiv.appendChild(dropDown);

    makeToggleButton(button, dropDown);
  }
  map.controls[google.maps.ControlPosition.TOP_RIGHT].push(parentDiv);
}


/**
 * Create a label for a single line in the legend.
 * @return {Node} The label.
 */
function createLegendLabel(text, color) {
  var outerLabel = document.createElement('div');
  var icon = document.createElement('div');
  var label = document.createElement('span');
  icon.className = 'party';
  label.className = 'marginlabel';
  label.innerHTML = text;
  icon.className = 'party';
  icon.style.backgroundColor = color;
  outerLabel.appendChild(icon);
  outerLabel.appendChild(label);
  return outerLabel;
}


/**
 * At startup, initialise the layer map with the name-to-index mapping.
 */
function initialiseLayerMap() {
  for (var i = 0; i < kml.length; ++i) {
    var spec = kml[i];
    layerMap[spec[5]] = i;
  }
}

initialiseLayerMap();

// Extern intialize so that closure does not remove it.
window['initialize'] = initialize;
